/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright 
     notice, this list of conditions and the following disclaimer in 
     the documentation and/or other materials provided with the distribution.

  3. The names of the authors may not be used to endorse or promote products
     derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jcraft.jsch;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

import static com.jcraft.jsch.Session.SSH_MSG_CHANNEL_DATA;
import static com.jcraft.jsch.Session.SSH_MSG_CHANNEL_OPEN;

/**
 * 2025.7.17：应用层的入口类
 * 2025.6.28: 抽象类,
 * <p>
 * 2025.6.30: 按照SSH协议架构，标准将SSH分成三层：传输层协议、用户认证协议、连接协议
 * 2025.6.30: 1) 最底层的是传输层协议，它负责认证服务器，加密数据，确保数据完整性，虽然它运行在TCP之上，
 * 但其实它可以运行在任意可靠的数据流之上；
 * 2025.6.30: 2) 第二层是用户认证协议，它负责认证使用者是否是ssh服务器的用户，
 * Public Key Authentication登陆ssh就将在这一层实现；
 * 2025.6.30: 3) 最上层是连接协议，它将把多路加密的通道转换成逻辑上的Channel
 * <p>
 * 2025.7.10：开启远程命令调用(exec)、sftp、shell窗口
 * 在传输层协议之上，启用连接协议的方式就是请求一个service name为ssh-connection服务
 * 2025.7.11: 通道的抽象类，通道就是ssh协议的一个应用
 * <p>
 * 2025.7.15：通道只是简单实现，这不是jsch工程的重点。ssh协议的重点是第一层的加密算法
 */
@Slf4j
public abstract class Channel implements Runnable {

    static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91;
    static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92;
    static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93;

    /**
     * 2025.7.2: 比如被请求开启Channel的一方不支持标注的channel type，
     * 2025.7.2: 那它将简单地回应SSH_MSG_CHANNEL_OPEN_FAILURE。
     * 2025.7.2: 请求方则或许需要显示description给用户。下面是一些预定义的错误码
     * 错误码0x00000005 - 0xFDFFFFFF将按照IETF CONSENSUS的方式分配，0xFE000000 - 0xFFFFFFFF则留给个人使用。
     * 虽然IANA没有关于0xFE000000 - 0xFFFFFFFF的控制权，但是还是将他约定成2部分使用：
     * <p>
     * 2025.7.2: 0xFE000000 - 0xFEFFFFFF被用在本地分配的Channel上，
     * 比如channel type为”example_session@example.com”(带有@符号)的Channel打开失败，
     * 那么错误码应该使用由IANA分配的部分（ 0x00000001 - 0xFDFFFFFF）或者本地分配相关的部分（0xFE000000 - 0xFEFFFFFF）。
     * 比如服务器不认识这个channel type，哪怕这个type是本地定义（包含@）的，也必须使用0x00000003错误码。
     * 然而如果，服务器认识这个错误码但是无法打开，则应该使用0xFE000000 - 0xFEFFFFFF其中的一个错误码。
     * 总的来说，参与者应该首先尝试使用IANA分配的错误码，然后在使用它们自定义的原因。
     * 对于从0xFF开始的部分，不做限制或建议。在这个范围内的每一个值，
     * 都不被期望有任何实际操作交互性，本质上说它们是为实验目的而预留的。
     * <p>
     */
    static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1;
    /**
     * 打开通道失败
     */
    static final int SSH_OPEN_CONNECT_FAILED = 2;
    /**
     * 不知道的通道
     */
    static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3;
    static final int SSH_OPEN_RESOURCE_SHORTAGE = 4;

    static int index = 0;
    private static java.util.Vector pool = new java.util.Vector();

    /**
     * 2025.6.27: 获取通道
     *
     * @param type
     * @return
     */
    static Channel getChannel(String type) {
        /**
         * 2025.6.30: 创建socket连接
         */
        if (type.equals("session")) {
            return new ChannelSession();
        }

        /**
         *  2025.6.30: 获取执行通道（属于ssh协议的第三层）
         */
        if (type.equals("shell")) {
            return new ChannelShell();
        }
        //2025.6.27: 获取执行通道（属于ssh协议的第三层）
        if (type.equals("exec")) {
            return new ChannelExec();
        }
        /**
         *  2025.6.30: 用不上的，先忽略
         */
        if (type.equals("x11")) {
            return new ChannelX11();
        }
        /**
         *  2025.6.30: 用不上的，先忽略
         */
        if (type.equals("auth-agent@openssh.com")) {
            return new ChannelAgentForwarding();
        }
        /**
         *  2025.6.30: 用不上的，先忽略
         */
        if (type.equals("direct-tcpip")) {
            return new ChannelDirectTCPIP();
        }
        /**
         *  2025.6.30: 用不上的，先忽略
         */
        if (type.equals("forwarded-tcpip")) {
            return new ChannelForwardedTCPIP();
        }
        /**
         *  2025.6.27: 获取sftp通道（属于ssh协议的第三层）
         */
        if (type.equals("sftp")) {
            return new ChannelSftp();
        }
        /**
         *  2025.6.30: 用不上的，先忽略
         */
        if (type.equals("subsystem")) {
            return new ChannelSubsystem();
        }
        return null;
    }

    /**
     * 2025.6.28: 获取pool中的channel(ssh协议的第三层)
     *
     * @param id
     * @param session
     * @return
     */
    static Channel getChannel(int id, Session session) {
        synchronized (pool) {
            for (int i = 0; i < pool.size(); i++) {
                Channel c = (Channel) (pool.elementAt(i));
                if (c.id == id && c.session == session) return c;
            }
        }
        return null;
    }

    /**
     * 2025.6.27: 删除通道池中的元素
     *
     * @param c
     */
    static void del(Channel c) {
        synchronized (pool) {
            pool.removeElement(c);
        }
    }

    int id;
    volatile int recipient = -1;
    protected byte[] type = Util.str2byte("foo");
    volatile int lwsize_max = 0x100000;
    volatile int lwsize = lwsize_max;     // local initial window size
    volatile int lmpsize = 0x4000;     // local maximum packet size

    volatile long rwsize = 0;         // remote initial window size
    volatile int rmpsize = 0;        // remote maximum packet size

    IO io = null;
    Thread thread = null;

    volatile boolean eof_local = false;
    volatile boolean eof_remote = false;

    volatile boolean close = false;
    volatile boolean connected = false;
    volatile boolean open_confirmation = false;

    volatile int exitstatus = -1;

    volatile int reply = 0;
    volatile int connectTimeout = 0;

    private Session session;

    int notifyme = 0;

    Channel() {
        synchronized (pool) {
            id = index++;
            pool.addElement(this);
        }
    }

    synchronized void setRecipient(int foo) {
        this.recipient = foo;
        if (notifyme > 0)
            notifyAll();
    }

    /**
     * 2025.7.2: 收据
     *
     * @return
     */
    int getRecipient() {
        return recipient;
    }

    void init() throws JSchException {
    }

    /**
     * 2025.6.27: 连接
     * <p>
     * 2025.7.11: 入口函数
     *
     * @throws JSchException
     */
    public void connect() throws JSchException {
        connect(0);
    }

    /**
     * 2025.6.27: 连接
     * 2025.6.27: 发送ssh协议打开通道报文
     *
     * @param connectTimeout
     * @throws JSchException
     */
    public void connect(int connectTimeout) throws JSchException {
        this.connectTimeout = connectTimeout;
        try {
//            JSch.logInfo("ssh建立数据通道连接");

            //2025.6.27: 发送包来打开通道
            sendChannelOpen();
            log.info("[ssh协议的第三层] 发送报文, 打开通道SSH_MSG_CHANNEL_OPEN");
            //2025.6.27: 启动线程
            start();
        } catch (Exception e) {
            connected = false;
            disconnect();
            if (e instanceof JSchException)
                throw (JSchException) e;
            throw new JSchException(e.toString(), e);
        }
    }

    public void setXForwarding(boolean foo) {
    }

    public void start() throws JSchException {
    }

    public boolean isEOF() {
        return eof_remote;
    }

    /**
     * 2025.6.30: 获取数据
     *
     * @param buf
     */
    void getData(Buffer buf) {
        setRecipient(buf.getInt());
        setRemoteWindowSize(buf.getUInt());
        setRemotePacketSize(buf.getInt());
    }

    public void setInputStream(InputStream in) {
        io.setInputStream(in, false);
    }

    public void setInputStream(InputStream in, boolean dontclose) {
        io.setInputStream(in, dontclose);
    }

    public void setOutputStream(OutputStream out) {
        io.setOutputStream(out, false);
    }

    public void setOutputStream(OutputStream out, boolean dontclose) {
        io.setOutputStream(out, dontclose);
    }

    public void setExtOutputStream(OutputStream out) {
        io.setExtOutputStream(out, false);
    }

    public void setExtOutputStream(OutputStream out, boolean dontclose) {
        io.setExtOutputStream(out, dontclose);
    }

    /**
     * 2025.6.28: System.in(0)
     * <p>
     * 2025.6.28: jsch自己写了一个IO类
     *
     * @return
     * @throws IOException
     */
    public InputStream getInputStream() throws IOException {
        int max_input_buffer_size = 32 * 1024;
        try {
            max_input_buffer_size =
                    Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
        } catch (Exception e) {
        }
        PipedInputStream in =
                new MyPipedInputStream(
                        32 * 1024,  // this value should be customizable.
                        max_input_buffer_size
                );
        boolean resizable = 32 * 1024 < max_input_buffer_size;
        io.setOutputStream(new PassiveOutputStream(in, resizable), false);
        return in;
    }

    /**
     * 2025.6.28: 获取扩展的输入流
     *
     * @return
     * @throws IOException
     */
    public InputStream getExtInputStream() throws IOException {
        int max_input_buffer_size = 32 * 1024;
        try {
            max_input_buffer_size =
                    Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
        } catch (Exception e) {
        }
        PipedInputStream in =
                new MyPipedInputStream(
                        32 * 1024,  // this value should be customizable.
                        max_input_buffer_size
                );
        boolean resizable = 32 * 1024 < max_input_buffer_size;
        io.setExtOutputStream(new PassiveOutputStream(in, resizable), false);
        return in;
    }

    /**
     * 2025.6.28: System.out(1)
     *
     * @return
     * @throws IOException
     */
    public OutputStream getOutputStream() throws IOException {

        final Channel channel = this;
        OutputStream out = new OutputStream() {
            private int dataLen = 0;
            private Buffer buffer = null;
            private Packet packet = null;
            private boolean closed = false;

            /**
             * 2025.6.28: 初始化报文的缓存设置
             *
             * @throws java.io.IOException
             */
            private synchronized void init() throws java.io.IOException {
                buffer = new Buffer(rmpsize);
                packet = new Packet(buffer);

                byte[] _buf = buffer.buffer;
                if (_buf.length - (14 + 0) - Session.buffer_margin <= 0) {
                    buffer = null;
                    packet = null;
                    throw new IOException("failed to initialize the channel.");
                }

            }

            byte[] b = new byte[1];

            public void write(int w) throws java.io.IOException {
//                JSch.logInfo("====write111====, threadName:" + Thread.currentThread().getName());
                b[0] = (byte) w;
                write(b, 0, 1);
            }


            /**
             * 2025.7.12: 入口函数
             *
             * @param buf
             * @param s
             * @param l
             * @throws java.io.IOException
             */
            public void write(byte[] buf, int s, int l) throws java.io.IOException {
//                JSch.logInfo("====write112====, threadName:"
//                        + Thread.currentThread().getName()
//                        + ", command is:" + new String(buf));
                if (packet == null) {
                    init();
                }

                if (closed) {
                    throw new java.io.IOException("Already closed");
                }

                byte[] _buf = buffer.buffer;
                int _bufl = _buf.length;
                while (l > 0) {
                    int _l = l;
                    if (l > _bufl - (14 + dataLen) - Session.buffer_margin) {
                        _l = _bufl - (14 + dataLen) - Session.buffer_margin;
                    }

                    if (_l <= 0) {
                        flush();
                        continue;
                    }

                    System.arraycopy(buf, s, _buf, 14 + dataLen, _l);
                    dataLen += _l;
                    s += _l;
                    l -= _l;
                }
            }

            /**
             * 2025.6.28: 往网络中发送报文
             * @throws java.io.IOException
             */
            public void flush() throws java.io.IOException {
//                JSch.logInfo("====flush====, threadName:" + Thread.currentThread().getName());
                if (closed) {
                    throw new java.io.IOException("Already closed");
                }
                if (dataLen == 0)
                    return;
                packet.reset();
                JSch.logInfo("发送通道数据: SSH_MSG_CHANNEL_DATA, threadName:"
                        + Thread.currentThread().getName());
                buffer.putByte((byte) SSH_MSG_CHANNEL_DATA);
                buffer.putInt(recipient);
                buffer.putInt(dataLen);
                buffer.skip(dataLen);
                try {
                    int foo = dataLen;
                    dataLen = 0;
                    synchronized (channel) {
                        if (!channel.close)
                            getSession().write(packet, channel, foo);
                    }
                } catch (Exception e) {
                    close();
                    throw new java.io.IOException(e.toString());
                }

            }

            public void close() throws java.io.IOException {
                if (packet == null) {
                    try {
                        init();
                    } catch (java.io.IOException e) {
                        // close should be finished silently.
                        return;
                    }
                }
                if (closed) {
                    return;
                }
                if (dataLen > 0) {
                    flush();
                }
                channel.eof();
                closed = true;
            }
        };
        return out;
    }

    class MyPipedInputStream extends PipedInputStream {
        private int BUFFER_SIZE = 1024;
        private int max_buffer_size = BUFFER_SIZE;

        MyPipedInputStream() throws IOException {
            super();
        }

        MyPipedInputStream(int size) throws IOException {
            super();
            buffer = new byte[size];
            BUFFER_SIZE = size;
            max_buffer_size = size;
        }

        MyPipedInputStream(int size, int max_buffer_size) throws IOException {
            this(size);
            this.max_buffer_size = max_buffer_size;
        }

        MyPipedInputStream(PipedOutputStream out) throws IOException {
            super(out);
        }

        MyPipedInputStream(PipedOutputStream out, int size) throws IOException {
            super(out);
            buffer = new byte[size];
            BUFFER_SIZE = size;
        }

        /*
         * TODO: We should have our own Piped[I/O]Stream implementation.
         * Before accepting data, JDK's PipedInputStream will check the existence of
         * reader thread, and if it is not alive, the stream will be closed.
         * That behavior may cause the problem if multiple threads make access to it.
         */
        public synchronized void updateReadSide() throws IOException {
            if (available() != 0) { // not empty
                return;
            }
            in = 0;
            out = 0;
            buffer[in++] = 0;
            read();
        }

        private int freeSpace() {
            int size = 0;
            if (out < in) {
                size = buffer.length - in;
            } else if (in < out) {
                if (in == -1) size = buffer.length;
                else size = out - in;
            }
            return size;
        }

        synchronized void checkSpace(int len) throws IOException {
            int size = freeSpace();
            if (size < len) {
                int datasize = buffer.length - size;
                int foo = buffer.length;
                while ((foo - datasize) < len) {
                    foo *= 2;
                }

                if (foo > max_buffer_size) {
                    foo = max_buffer_size;
                }
                if ((foo - datasize) < len) return;

                byte[] tmp = new byte[foo];
                if (out < in) {
                    System.arraycopy(buffer, 0, tmp, 0, buffer.length);
                } else if (in < out) {
                    if (in == -1) {
                    } else {
                        System.arraycopy(buffer, 0, tmp, 0, in);
                        System.arraycopy(buffer, out,
                                tmp, tmp.length - (buffer.length - out),
                                (buffer.length - out));
                        out = tmp.length - (buffer.length - out);
                    }
                } else if (in == out) {
                    System.arraycopy(buffer, 0, tmp, 0, buffer.length);
                    in = buffer.length;
                }
                buffer = tmp;
            } else if (buffer.length == size && size > BUFFER_SIZE) {
                int i = size / 2;
                if (i < BUFFER_SIZE) i = BUFFER_SIZE;
                byte[] tmp = new byte[i];
                buffer = tmp;
            }
        }
    }

    void setLocalWindowSizeMax(int foo) {
        this.lwsize_max = foo;
    }

    void setLocalWindowSize(int foo) {
        this.lwsize = foo;
    }

    void setLocalPacketSize(int foo) {
        this.lmpsize = foo;
    }

    synchronized void setRemoteWindowSize(long foo) {
        this.rwsize = foo;
    }

    synchronized void addRemoteWindowSize(long foo) {
        this.rwsize += foo;
        if (notifyme > 0)
            notifyAll();
    }

    void setRemotePacketSize(int foo) {
        this.rmpsize = foo;
    }

    /**
     * 2025.6.27：抽象类，不会实现报文的处理；交给子类实现
     */
    public void run() {
    }

    void write(byte[] foo) throws IOException {
        write(foo, 0, foo.length);
    }

    void write(byte[] foo, int s, int l) throws IOException {
        try {
            io.put(foo, s, l);
        } catch (NullPointerException e) {
        }
    }

    /**
     * 2025.6.30: 扩展输出
     *
     * @param foo
     * @param s
     * @param l
     * @throws IOException
     */
    void write_ext(byte[] foo, int s, int l) throws IOException {
        try {
            io.put_ext(foo, s, l);
        } catch (NullPointerException e) {
        }
    }

    void eof_remote() {
        eof_remote = true;
        try {
            io.out_close();
        } catch (NullPointerException e) {
        }
    }

    /**
     * 2025.6.28: 发送EOF包
     */
    void eof() {
        if (eof_local) return;
        eof_local = true;

        int i = getRecipient();
        if (i == -1) return;

        try {
            Buffer buf = new Buffer(100);
            Packet packet = new Packet(buf);
            packet.reset();
            buf.putByte((byte) Session.SSH_MSG_CHANNEL_EOF);
            buf.putInt(i);
            synchronized (this) {
                if (!close)
                    getSession().write(packet);
            }
        } catch (Exception e) {
            //System.err.println("Channel.eof");
            //e.printStackTrace();
        }
    /*
    if(!isConnected()){ disconnect(); }
    */
    }

    /*
    http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt

  5.3  Closing a Channel
    When a party will no longer send more data to a channel, it SHOULD
     send SSH_MSG_CHANNEL_EOF.

              byte      SSH_MSG_CHANNEL_EOF
              uint32    recipient_channel

    No explicit response is sent to this message.  However, the
     application may send EOF to whatever is at the other end of the
    channel.  Note that the channel remains open after this message, and
     more data may still be sent in the other direction.  This message
     does not consume window space and can be sent even if no window space
     is available.

       When either party wishes to terminate the channel, it sends
       SSH_MSG_CHANNEL_CLOSE.  Upon receiving this message, a party MUST
     send back a SSH_MSG_CHANNEL_CLOSE unless it has already sent this
     message for the channel.  The channel is considered closed for a
       party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and
     the party may then reuse the channel number.  A party MAY send
     SSH_MSG_CHANNEL_CLOSE without having sent or received
     SSH_MSG_CHANNEL_EOF.

              byte      SSH_MSG_CHANNEL_CLOSE
              uint32    recipient_channel

     This message does not consume window space and can be sent even if no
     window space is available.

     It is recommended that any data sent before this message is delivered
       to the actual destination, if possible.
       2025.6.27: 关闭
    */
    void close() {
        if (close) return;
        close = true;
        eof_local = eof_remote = true;

        int i = getRecipient();
        if (i == -1) return;

        try {
            Buffer buf = new Buffer(100);
            Packet packet = new Packet(buf);
            packet.reset();
            //2025.6.27: 发送关闭通道报文
            buf.putByte((byte) Session.SSH_MSG_CHANNEL_CLOSE);
            buf.putInt(i);
            synchronized (this) {
                getSession().write(packet);
            }
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }

    public boolean isClosed() {
        return close;
    }

    /**
     * 2025.6.27: 关闭会话（这里不会关闭socket，只会发送ssh协议关闭会话报文）
     * <p>
     * 2025.6.30: 关闭数据通道（属于ssh协议的第三层）
     *
     * @param session
     */
    static void disconnect(Session session) {
        Channel[] channels = null;
        int count = 0;
        synchronized (pool) {
            channels = new Channel[pool.size()];
            for (int i = 0; i < pool.size(); i++) {
                try {
                    Channel c = ((Channel) (pool.elementAt(i)));
                    if (c.session == session) {
                        channels[count++] = c;
                    }
                } catch (Exception e) {
                }
            }
        }
        for (int i = 0; i < count; i++) {
            channels[i].disconnect();
        }
    }

    /**
     * 2025.6.27: 发送ssh协议关闭通道报文；
     * 2025.6.28: 同时关闭IO
     * <p>
     * 2025.6.30: 关闭数据通道（属于ssh协议的第三层）
     * *
     */
    public void disconnect() {
        //System.err.println(this+":disconnect "+io+" "+connected);
        //Thread.dumpStack();

        try {

            synchronized (this) {
                if (!connected) {
                    return;
                }
                connected = false;
            }
            //2025.6.27: 发送关闭通道报文
            close();

            eof_remote = eof_local = true;

            thread = null;

            try {
                if (io != null) {
                    //2025.6.27: 关闭IO
                    io.close();
                }
            } catch (Exception e) {
                //e.printStackTrace();
            }
            // io=null;
        } finally {
            Channel.del(this);
        }

        log.info("ssh数据通道断开连接");
    }

    public boolean isConnected() {
        Session _session = this.session;
        if (_session != null) {
            return _session.isConnected() && connected;
        }
        return false;
    }

    public void sendSignal(String signal) throws Exception {
        RequestSignal request = new RequestSignal();
        request.setSignal(signal);
        request.request(getSession(), this);
    }

//  public String toString(){
//      return "Channel: type="+new String(type)+",id="+id+",recipient="+recipient+",window_size="+window_size+",packet_size="+packet_size;
//  }

/*
  class OutputThread extends Thread{
    Channel c;
    OutputThread(Channel c){ this.c=c;}
    public void run(){c.output_thread();}
  }
*/

    /**
     * 2025.6.28: 消极输入流
     */
    class PassiveInputStream extends MyPipedInputStream {
        PipedOutputStream out;

        PassiveInputStream(PipedOutputStream out, int size) throws IOException {
            super(out, size);
            this.out = out;
        }

        PassiveInputStream(PipedOutputStream out) throws IOException {
            super(out);
            this.out = out;
        }

        public void close() throws IOException {
            if (out != null) {
                this.out.close();
            }
            out = null;
        }
    }

    /**
     * 2025.6.28: 消极输出流
     */
    class PassiveOutputStream extends PipedOutputStream {
        private MyPipedInputStream _sink = null;

        PassiveOutputStream(PipedInputStream in,
                            boolean resizable_buffer) throws IOException {
            super(in);
            if (resizable_buffer && (in instanceof MyPipedInputStream)) {
                this._sink = (MyPipedInputStream) in;
            }
        }

        public void write(int b) throws IOException {
            if (_sink != null) {
                _sink.checkSpace(1);
            }
            super.write(b);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            if (_sink != null) {
                _sink.checkSpace(len);
            }
            super.write(b, off, len);
        }
    }

    /**
     * 2025.6.27: 设置退出状态
     *
     * @param status
     */
    void setExitStatus(int status) {
        exitstatus = status;
    }

    public int getExitStatus() {
        return exitstatus;
    }

    void setSession(Session session) {
        this.session = session;
    }

    /**
     * 2025.6.27: 获取会话
     *
     * @return
     * @throws JSchException
     */
    public Session getSession() throws JSchException {
        Session _session = session;
        if (_session == null) {
            throw new JSchException("session is not available");
        }
        return _session;
    }

    public int getId() {
        return id;
    }

    /**
     * 2025.6.28: 发送打开通道确认报文
     *
     * @throws Exception
     */
    protected void sendOpenConfirmation() throws Exception {
        Buffer buf = new Buffer(100);
        Packet packet = new Packet(buf);
        packet.reset();
        buf.putByte((byte) SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
        buf.putInt(getRecipient());
        buf.putInt(id);
        buf.putInt(lwsize);
        buf.putInt(lmpsize);
        getSession().write(packet);
    }

    /**
     * 2025.7.2: 发送打开通道失败报文
     *
     * @param reasoncode
     */
    protected void sendOpenFailure(int reasoncode) {
        try {
            Buffer buf = new Buffer(100);
            Packet packet = new Packet(buf);
            packet.reset();
            buf.putByte((byte) SSH_MSG_CHANNEL_OPEN_FAILURE);
            buf.putInt(getRecipient());
            buf.putInt(reasoncode);
            buf.putString(Util.str2byte("open failed"));
            buf.putString(Util.empty);
            getSession().write(packet);
        } catch (Exception e) {
        }
    }

    /**
     * 2025.6.27: 生成开通通道的packet
     *
     * @return
     */
    protected Packet genChannelOpenPacket() {
        Buffer buf = new Buffer(100);
        Packet packet = new Packet(buf);
        // byte   SSH_MSG_CHANNEL_OPEN(90)
        // string channel type         //
        // uint32 sender channel       // 0
        // uint32 initial window size  // 0x100000(65536)
        // uint32 maxmum packet size   // 0x4000(16384)
        packet.reset();
        buf.putByte((byte) SSH_MSG_CHANNEL_OPEN);
        buf.putString(this.type);
        buf.putInt(this.id);
        buf.putInt(this.lwsize);
        buf.putInt(this.lmpsize);
        return packet;
    }

    /**
     * 2025.6.27: 发送打开通道报文
     *
     * @throws Exception
     */
    protected void sendChannelOpen() throws Exception {
        Session _session = getSession();
        if (!_session.isConnected()) {
            throw new JSchException("session is down");
        }

        //2025.6.27: 生成打开通道的packet
        Packet packet = genChannelOpenPacket();
        //2025.6.27: 写入打开通道的报文
        _session.write(packet);

        int retry = 2000;
        long start = System.currentTimeMillis();
        long timeout = connectTimeout;
        if (timeout != 0L) retry = 1;
        synchronized (this) {
            while (this.getRecipient() == -1 &&
                    _session.isConnected() &&
                    retry > 0) {
                if (timeout > 0L) {
                    if ((System.currentTimeMillis() - start) > timeout) {
                        retry = 0;
                        continue;
                    }
                }
                try {
                    long t = timeout == 0L ? 10L : timeout;
                    this.notifyme = 1;
                    wait(t);
                } catch (java.lang.InterruptedException e) {
                } finally {
                    this.notifyme = 0;
                }
                retry--;
            }
        }
        if (!_session.isConnected()) {
            throw new JSchException("session is down");
        }
        if (this.getRecipient() == -1) {  // timeout
            throw new JSchException("channel is not opened.");
        }
        if (this.open_confirmation == false) {  // SSH_MSG_CHANNEL_OPEN_FAILURE
            throw new JSchException("channel is not opened.");
        }
        connected = true;
    }
}
